/**
 * Mysql数据库C++连接池封装
 * 
 * 开发者   : Marco
 * 创建时间 : 2017年4月6日, 下午12:03
 */

#ifndef __MARCO_MYSQL_POOL_H__
#define __MARCO_MYSQL_POOL_H__

#if (defined(__cplusplus) && __cplusplus >= 201103L)
#define MYSQLPP_MYSQL_HEADERS_BURIED
#if defined(MYSQLPP_MYSQL_HEADERS_BURIED)
#       include <mysql++/connection.h>
#else
#       include <connection.h>
#endif

#include <list>
#include <mutex> 
#include <string>
#include <memory>
#include <condition_variable>

namespace marco {
namespace mysql {

typedef mysqlpp::Connection			connection;
typedef mysqlpp::Connection*			connection_ptr;
typedef std::shared_ptr<mysqlpp::Connection>	connection_sptr;

/**
 * 基类, 禁止构造对象
 */
class poolbase {
public:
    /**
     * 构造
     * @param db 数据库名
     * @param server 服务器地址
     * @param user 用户名
     * @param pwd 密码
     * @param port 端口
     * @param size 初始连接数
     * @param exception 是否启用异常
     * @exception 启用异常时, 有初始连接数但连接失败时，会抛出mysqlpp::ConnectionFailed(const char*, int)的异常
     */
    poolbase(const char* db, const char* server, const char* user, const char* pwd, unsigned short port, int size, bool exception);
    
    /**
     * 是否空连接池
     * @return true(是), false(否)
     */
    bool empty() const { return (size_ == 0); }
    
    /**
     * 获取当前池中连接数
     * @return 当前连接池中连接数
     */
    int size() const { return size_; }
    
    /**
     * 重新按创建时参数连接
     * @param conn 连接对象
     * @return 连接结果
     */
    bool reconnect(connection_ptr conn);
    
    /**
     * 获取一条连接
     * @return 连接对象
     */
    virtual connection_ptr grab() = 0;
    
    /**
     * 交还一条连接
     * @param pc 连接对象
     */
    virtual void release(const connection_ptr pc) {
	pool_.push_back(pc);
	size_ += 1;
    }
    
    /**
     * 销毁一条连接
     * @param pc 连接对象
     */
    void destroy(connection_ptr pc) { delete pc; }
    
    /**
     * 从池中获取一条连接，并使用智能指针包装，引用计数为0时自动交还到池中
     * @param pool 连接池指针
     * @return 连接智能指针
     */
    connection_sptr grab_sptr() {
	connection_ptr pc = grab();
	connection_sptr sptr(pc, [&](connection_ptr pc) { release(pc); });
	return sptr;
    }
    
protected:
    typedef std::list<connection_ptr>::iterator iterator;
    virtual ~poolbase() { };
    poolbase(const poolbase& no_copies); // 禁用拷贝构造以及赋值
    const poolbase& operator=(const poolbase& no_copies);

    /**
     * 清空当前池中所有空闲连接，已获取尚未交还的连接则不会清除
     */
    void clear(void);
    
    /**
     * 创建一条新连接
     * @return 连接对象
     * @exception 连接失败会抛出mysqlpp::ConnectionFailed(const char*, int)的异常
     */
    connection_ptr create(void);
    
    std::list<connection_ptr> pool_;
    std::string db_, server_, user_, pwd_;
    int size_;
    unsigned short port_;
    bool exception_;
};

/**
 * 连接池类, 当池中没有连接数时会创建, 适用于与线程池配合使用, 工作线程数与数据库连接数一致, 这样不会新建连接
 */
class pool : public poolbase {
public:
    using poolbase::poolbase; // 使用基类构造
    ~pool() { std::unique_lock<std::mutex> lck(mtx_); clear(); }

    /**
     * 从连接池中获取一条连接
     * @return 连接对象
     */
    connection_ptr grab() override;
    
    /**
     * 交还一条连接到连接池
     * @param pc 连接对象
     */
    void release(const connection_ptr pc) override;

private:
    pool(const pool& no_copies); // 禁用拷贝构造以及赋值
    const pool& operator=(const pool& no_copies);
    
    std::mutex mtx_;
};

/**
 * 连接池类, 当池中没有连接数时会阻塞等待, 直到有新连接为止, 适用于线程数比连接数多, 共用连接.
 */
class waitpool : public poolbase {
public:
    using poolbase::poolbase; // 使用基类构造
    ~waitpool() { std::unique_lock<std::mutex> lck(mtx_); clear(); }

    /**
     * 从连接池中获取一条连接
     * @return 连接对象
     */
    connection_ptr grab() override;

    /**
     * 交还一条连接到连接池
     * @param pc 连接对象
     */
    void release(const connection_ptr pc) override;

private:
    waitpool(const waitpool& no_copies); // 禁用拷贝构造以及赋值
    const waitpool& operator=(const waitpool& no_copies);
    
    std::mutex mtx_;
    std::condition_variable cv_;
};

/**
 * 封装从池中获取的连接对象, 构造时申请连接, 析构时自动交还
 */
class scoped {
public:
    explicit scoped(poolbase* pool) : pool_(pool), connection_(pool->grab()) { }
    ~scoped() { pool_->release(connection_); } // 析构自动交还

    connection_ptr operator->() const { return connection_; }
    connection& operator*() const { return *connection_; }
    connection_ptr get() const { return connection_; }

private:
    scoped(const scoped& no_copies); // 禁用拷贝与赋值
    const scoped& operator=(const scoped& no_copies);

    poolbase* pool_;
    connection_ptr const connection_;
};

}; // end namespace mysql
}; // end namespace marco

#endif // #if (defined(__cplusplus) && __cplusplus >= 201103L)
#endif /* __MARCO_MYSQL_POOL_H__ */